package org.xx.armory.db.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.db.DbException;
import org.xx.armory.db.Transaction;
import org.xx.armory.db.TransactionManager;
import org.xx.armory.db.TransactionProperties;

import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

import static org.xx.armory.commons.Validators.notNull;

/**
 * 本地事务管理器。
 * <p>事务直接从本地获取，此事务管理器同一时间内只维护一个事务，默认允许自动提交。</p>
 */
public final class LocalTransactionManager
        extends AbstractTransactionManager
        implements TransactionManager, AutoCloseable {
    private final Logger logger = LoggerFactory.getLogger(LocalTransactionManager.class);
    private final AtomicReference<TransactionProperties> properties;
    private final AtomicReference<MonopolyTransaction> transaction;

    public LocalTransactionManager() {
        this.properties = new AtomicReference<>(new TransactionProperties());
        this.transaction = new AtomicReference<>();
    }

    /**
     * 获取事务的属性。
     *
     * @return 事务的属性。
     */
    public final TransactionProperties getProperties() {
        return this.properties.get();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void setProperties(
            TransactionProperties properties
    ) {
        notNull(properties, "properties");

        final TransactionProperties oldProperties = this.properties.get();
        if (!Objects.equals(oldProperties, properties)) {
            if (this.properties.compareAndSet(oldProperties, properties)) {
                transactionPropertiesChanged(oldProperties, properties);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Transaction current()
            throws DbException {
        MonopolyTransaction transaction = this.transaction.get();
        if (transaction == null || !transaction.isActive()) {
            try {
                final MonopolyTransaction newTransaction = new MonopolyTransaction(getDataSource(), getProperties());
                if (!this.transaction.compareAndSet(transaction, newTransaction)) {
                    newTransaction.close();
                }
            } catch (Exception ex) {
                throw new IllegalStateException("cannot create local transaction", ex);
            }
        }

        return this.transaction.get();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void transactionPropertiesChanged(
            TransactionProperties oldProperties,
            TransactionProperties newProperties
    ) {
        MonopolyTransaction transaction = this.transaction.get();
        if (transaction != null && transaction.isActive()) {
            if (transaction.getUsers().isEmpty()) {
                try {
                    logger.info("active transaction should be closed since properties changed");
                    transaction.close();
                } catch (Exception ex) {
                    logger.warn("cannot close transaction", ex);
                } finally {
                    this.transaction.compareAndSet(transaction, null);
                }
            } else {
                throw new IllegalStateException("transaction is alive and has users");
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close()
            throws Exception {
        MonopolyTransaction transaction = this.transaction.get();
        if (transaction != null && transaction.isActive()) {
            try {
                transaction.close();
            } finally {
                this.transaction.compareAndSet(transaction, null);
            }
        }
    }
}
